package org.fujene.communicate.repli;

import java.util.ArrayList;
import java.util.TreeSet;
import java.util.logging.Logger;

import org.fujene.structure.ExtendQueue;
import org.fujene.structure.ExtendTerm;
import org.fujene.structure.LookupPointer;
import org.fujene.structure.NodeInfo;
import org.msgpack.MessagePackObject;
import org.msgpack.object.ArrayType;
import org.msgpack.object.BooleanType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.NilType;
import org.msgpack.object.RawType;
import org.msgpack.rpc.Request;

public class ReplicatePeerHandler {
	private static final int OUTER = ReplicatePeerSystem.OUTER;
	private static final int INNER = ReplicatePeerSystem.INNER;
	private Logger logger;
	private ReplicatePeerSystem parentSystem;
	private int contentNum;
	private int supplementNum;

	public ReplicatePeerHandler(Logger logger, ReplicatePeerSystem parentSystem,
			int contentNum, int supplementNum) {
		this.logger = logger;
		this.parentSystem = parentSystem;
		this.contentNum = contentNum;
		this.supplementNum = supplementNum;
	}

	/**
	 * ノードチェーンを送信します。
	 * 
	 * @param req
	 *            ... 0: (IntegerType/int)ノードサークル種別
	 */
	public void circleList(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 1 || !objs[0].isIntegerType()) {
			req.sendError(RawType
					.create("ERROR(circleList): Argument is invalid"));
			logger.severe("ERROR(circleList): Argument is invalid");
			return;
		}

		MessagePackObject[] output = null;
		int count = 0;

		TreeSet<NodeInfo> currentInfos = new TreeSet<NodeInfo>(
				parentSystem.getNode(objs[0].asInt()));
		output = new MessagePackObject[2 * currentInfos.size()];

		for (NodeInfo eachPeer : currentInfos) {
			output[count++] = RawType.create(eachPeer.getAddr());
			output[count++] = IntegerType.create(eachPeer.getHash());
		}

		logger.finest("circleList - called.");

		req.sendResult(ArrayType.create(output));
	}

	/**
	 * ノードを登録します。
	 * 
	 * @param req
	 *            ... 0: (RawType/String)IPアドレス, 1: (IntegerType/int)本文チェーンハッシュ,
	 *            2: (IntegerType/int)辞書チェーンハッシュ
	 */
	public void register(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 3
				|| (!objs[0].isRawType() && !objs[1].isIntegerType() && !objs[2]
						.isIntegerType())) {
			req.sendError(RawType
					.create("ERROR(register): Argument is invalid"));
			logger.severe("ERROR(register): Argument is invalid");
			return;
		}

		parentSystem.addReplication(OUTER, new NodeInfo(objs[0].asString(),
				objs[1].asInt()));
		parentSystem.addReplication(INNER, new NodeInfo(objs[0].asString(),
				objs[2].asInt()));

		logger.finest("register - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * ノードを削除します。
	 * 
	 * @param req
	 *            ... 0: (IntegerType/int)サークル種別, 1: (IntegerType/int)削除ハッシュ
	 */
	public void delete(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 2
				|| (!objs[0].isIntegerType() && !objs[1].isIntegerType())) {
			req.sendError(RawType.create("ERROR(delete): Argument is invalid"));
			logger.severe("ERROR(delete): Argument is invalid");
			return;
		}

		parentSystem.deleteReplication(objs[0].asInt(), new NodeInfo("Dummy",
				objs[1].asInt()));

		logger.finest("delete - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * データを送信します。(未使用)
	 * 
	 * @param req
	 *            ... 0: (IntegerType/int)サークル種別, 1: (ArrayType)データ(単語[本文], ID,
	 *            単語[本文], ID, ...)
	 */
	public void sendData(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		int i = 0;
		if (objs.length < 2
				|| (!objs[0].isIntegerType() && !objs[1].isArrayType())) {
			req.sendError(RawType
					.create("ERROR(sendData): Argument is invalid"));
			logger.severe("ERROR(sendData): Argument is invalid");
			return;
		}

		int type = objs[0].asInt();
		MessagePackObject[] datas = objs[1].asArray();
		if (type == OUTER)
			for (; i < datas.length; i += 3)
				parentSystem.invokeIndexOuter(datas[i].asString(),
						datas[i + 1].asLong(), datas[i + 2].asInt());
		else {
			String[] contents = new String[contentNum];
			String[] supplements = new String[supplementNum];
			int j;
			long ID;
			while (i < datas.length) {
				ID = datas[i].asLong();
				for (j = 0; j < contentNum; i++, j++) {
					contents[j] = datas[i].asString();
				}
				for (j = 0; j < supplementNum; i++, j++) {
					supplements[j] = datas[i].asString();
				}
				parentSystem.invokeIndexInner(new ExtendQueue(contents,
						supplements, ID));
			}
		}
		logger.finest("sendData - called.");

		req.sendResult(NilType.create());
	}

	// 溜め込み用
	private String recieverIP = null;
	private int dataType = -1;
	private ArrayList<?> dataList = null;

	/**
	 * 指定した範囲のデータを取得します。
	 * 
	 * @param req
	 *            ... 0: (IntegerType/int)データ番号, 1:
	 *            (RawType/String)自分のIPアドレス</p>
	 * @param 戻り値
	 *            ... (共通) 0: 単語[本文]
	 */
	public void getData(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();

		if (objs.length < 2
				|| (!objs[0].isIntegerType() && !objs[1].isRawType())) {
			req.sendError(RawType.create("ERROR(getData): Invalid argument"));
			logger.severe("ERROR(getData): Invalid argument");
			return;
		} else if (dataList == null) {
			req.sendError(RawType
					.create("ERROR(getData): Call \"getDataSize\" first"));
			logger.severe("ERROR(getData): Call \"getDataSize\" first");
			return;
		} else if (!objs[1].asString().equals(recieverIP)) {
			// 他のIPに送信中
			req.sendResult(BooleanType.create(false));
			return;
		}

		int index = objs[0].asInt();
		int fieldLength, outputLength;
		MessagePackObject[] output;
		if (dataType == OUTER) {
			fieldLength = ((ExtendTerm) (dataList.get(index))).firstIDs.length;
			outputLength = 2 + fieldLength;
			output = new MessagePackObject[outputLength];
			output[0] = RawType
					.create(((ExtendTerm) (dataList.get(index))).term);
			output[1] = IntegerType
					.create(((ExtendTerm) (dataList.get(index))).termHash);
			for (int i = 0; i < fieldLength; i++) {
				output[2 + 3 * i] = IntegerType.create(((ExtendTerm) (dataList
						.get(index))).firstIDs[i]);
				// ポインタも追加
				output[3 + 3 * i] = IntegerType.create(((ExtendTerm) (dataList
						.get(index))).invIndex[i].block);
				output[4 + 3 * i] = IntegerType.create(((ExtendTerm) (dataList
						.get(index))).invIndex[i].point);
			}
		} else {
			fieldLength = ((ExtendTerm) (dataList.get(index))).firstIDs.length;
			outputLength = 2 + fieldLength;
			output = new MessagePackObject[outputLength];

			output[0] = IntegerType
					.create(((ExtendQueue) (dataList.get(index))).ID);
			// 本文
			for (int i = 0; i < ((ExtendQueue) (dataList.get(index))).contents.length; i++) {
				output[i + 1] = RawType.create(((ExtendQueue) (dataList
						.get(index))).contents[i]);
			}
			// 付属文章
			for (int i = 0; i < ((ExtendQueue) (dataList.get(index))).supplements.length; i++) {
				output[i
						+ 1
						+ ((ExtendQueue) (dataList.get(index))).supplements.length] = RawType
						.create(((ExtendQueue) (dataList.get(index))).supplements[i]);
			}
		}

		req.sendResult(ArrayType.create(output));
	}

	/**
	 * 指定した範囲にあるデータサイズを取得し、データ転送の準備をします。
	 * 
	 * @param req
	 *            ... 0: (IntegerType/int)サークル種別, 1: (IntegerType/int)ハッシュ開始位置,
	 *            2: (IntegerType/int)ハッシュ終了位置, 3: (RawType/String)自分のIPアドレス
	 */
	public void getDataSize(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();

		if (objs.length < 4
				|| (!objs[0].isIntegerType() && !objs[1].isIntegerType()
						&& !objs[2].isIntegerType() && !objs[3].isRawType())) {
			req.sendError(RawType.create("ERROR(getData): Invalid argument"));
			logger.severe("ERROR(getData): Invalid argument");
			return;
		}

		recieverIP = objs[3].asString();
		dataType = objs[0].asInt();
		if (objs[0].asInt() == OUTER)
			dataList = parentSystem.getTermListByHash(objs[1].asInt(),
					objs[2].asInt(), objs[0].asInt());
		else
			dataList = parentSystem.getContentListByHash(objs[1].asInt(),
					objs[2].asInt());

		logger.finest("getDataSize - called.");

		req.sendResult(IntegerType.create(dataList.size()));
	}

	/**
	 * データ転送を終了します。
	 * 
	 * @param req
	 *            ... 0: (RawType/String)自分のIPアドレス
	 */
	public void endDataSend(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();

		if (objs.length < 1 || (!objs[0].isRawType())) {
			req.sendError(RawType
					.create("ERROR(endDataSend): Invalid argument"));
			logger.severe("ERROR(endDataSend): Invalid argument");
			return;
		}

		logger.finest("endDataSend - called.");

		if (objs[0].asString().equals(recieverIP)) {
			recieverIP = null;
			dataList.clear();
			dataList = null;
			dataType = -1;
			req.sendResult(BooleanType.create(true));
		} else
			req.sendResult(BooleanType.create(false));
	}

	/**
	 * 指定した範囲のデータを削除します。（未使用）
	 * 
	 * @param req
	 *            ... 0: (IntegerType)文章タイプ, 1: (IntegerType)ハッシュ開始位置, 2:
	 *            (IntegerType)ハッシュ終了位置
	 */
	public void deleteData(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 3
				|| (!objs[0].isIntegerType() && !objs[1].isIntegerType() && !objs[2]
						.isIntegerType())) {
			req.sendError(RawType.create("ERROR(deleteData): Invalid argument"));
			System.out.println("ERROR(deleteData): Invalid argument");
			return;
		}

		MessagePackObject[] output = null;

		logger.finest("deleteData - called.");

		req.sendResult(ArrayType.create(output));
	}

	/**
	 * 生存しているかどうか返します。
	 * 
	 * @param req
	 *            ... 引数なし
	 */
	public void isLive(Request req) {
		logger.finest("isLive - called.");

		req.sendResult(RawType.create("Live"));
	}

	/**
	 * 送られた文章をインデックスに追加します。
	 * 
	 * @param req
	 *            ... 0: (IntegerType)文章ID 1～: (RawType)文章
	 */
	public void indexContent(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		int i = 1;
		if (objs.length < 1 + contentNum + supplementNum
				|| (!objs[0].isIntegerType() && !objs[1].isRawType())) {
			req.sendError(NilType.create());
			logger.severe("ERROR(indexContent): Invalid argument");
			return;
		}

		long ID = objs[0].asLong();
		String[] contents = new String[contentNum];
		String[] supplements = new String[supplementNum];
		for (; i < 1 + contentNum; i++) {
			contents[i - 1] = objs[i].asString();
		}
		for (; i < 1 + contentNum + supplementNum; i++) {
			supplements[i - 1 - contentNum] = objs[i].asString();
		}
		parentSystem
				.invokeIndexInner(new ExtendQueue(contents, supplements, ID));

		// System.out.println("indexContent - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * 送られた単語をインデックスに追加します。
	 * 
	 * @param req
	 *            ... 0: (RawType)単語 1: (IntegerType)文章ID 2: (IntegerType)文章タイプ
	 */
	public void indexTerm(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 3
				|| (!objs[0].isRawType() && !objs[1].isIntegerType() && !objs[2]
						.isIntegerType())) {
			req.sendError(RawType.create("ERROR(index): Invalid argument"));
			logger.severe("ERROR(indexTerm): Invalid argument");
			return;
		}

		parentSystem.invokeIndexOuter(objs[0].asString(), objs[1].asLong(),
				objs[2].asInt());

		// System.out.println("indexTerm - called.");

		req.sendResult(NilType.create());
	}

	/**
	 * 送られた単語をインデックスに追加します。
	 * 
	 * @param req
	 *            ... 0: (RawType/String)単語 1: (IntegerType/int)文章タイプ
	 */
	public void searchTerm(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 2
				|| (!objs[0].isRawType() && !objs[1].isIntegerType())) {
			req.sendError(RawType.create("ERROR(search): Invalid argument"));
			logger.severe("ERROR(searchTerm): Invalid argument");
			return;
		}

		LookupPointer result = parentSystem.invokeSearch(objs[0].asString(),
				objs[1].asInt());

		logger.finest("searchTerm - called.");

		req.sendResult(ArrayType.create(new MessagePackObject[] {
				IntegerType.create(result.block),
				IntegerType.create(result.point),
				IntegerType.create(result.firstID) }));
	}

	/**
	 * 本文を送信します。
	 * 
	 * @param req
	 *            ... 0: (IntegerType/long)文章ID, 1: インデックス
	 */
	public void getContent(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		int i;
		if (objs.length < 2
				|| (!objs[0].isIntegerType() && !objs[1].isIntegerType())) {
			req.sendError(RawType.create("ERROR(getContent): Invalid argument"));
			logger.severe("ERROR(getContent): Invalid argument");
			return;
		}

		logger.finest("getContent - called.");

		ExtendQueue content = parentSystem.lookupContent(objs[0].asLong(),
				objs[1].asInt());
		objs = new MessagePackObject[content.contents.length
				+ content.supplements.length];
		for (i = 0; i < content.contents.length; i++) {
			objs[i] = RawType.create(content.contents[i]);
		}
		for (i = 0; i < content.supplements.length; i++) {
			objs[i + content.contents.length] = RawType
					.create(content.supplements[i]);
		}

		req.sendResult(ArrayType.create(objs));
	}

	/**
	 * 本文のIDポインタを取得します。
	 * 
	 * @param req
	 *            ... 0: (IntegerType/int)開始ポインタ(block), 1:
	 *            (IntegerType/int)開始ポインタ(point), 2: (IntegerType/long)開始ID, 3:
	 *            (IntegerType/int)長さ<br>
	 * 
	 *            戻り値 ... 0: 次の開始ポインタ(block), 1: 次の開始ポインタ(point), 2, 3, 4,...:
	 *            ID
	 */
	public void getList(Request req) {
		MessagePackObject obj = req.getArguments();
		MessagePackObject[] objs = obj.asArray();
		if (objs.length < 4
				|| (!objs[0].isIntegerType() || !objs[1].isIntegerType()
						|| !objs[2].isIntegerType() || !objs[3].isIntegerType())) {
			req.sendError(RawType.create("ERROR(getList): Invalid argument"));
			logger.severe("ERROR(getList): Invalid argument");
			return;
		}

		LookupPointer ptr = new LookupPointer(objs[0].asInt(), objs[1].asInt(),
				objs[2].asLong());
		ArrayList<Long> list = parentSystem.getContentsByHash(ptr,
				objs[3].asInt());

		MessagePackObject[] retval = new MessagePackObject[list.size() * 2 + 3];
		retval[0] = IntegerType.create(ptr.block);
		retval[1] = IntegerType.create(ptr.point);
		retval[2] = IntegerType.create(ptr.firstID);
		for (int i = 0; i < list.size(); i++) {
			retval[i + 3] = IntegerType.create(list.get(i));
		}

		// System.out.println("getList - called.");

		req.sendResult(ArrayType.create(retval));
	}

	/**
	 * ノード参加に必要な情報を各種送信します。 (BlockNum, BlockSize, Replication, IndexMode)
	 * 
	 * @param req
	 *            ... 引数なし
	 */
	public void getInfo(Request req) {
		logger.finest("getInfo - called.");
		req.sendResult(parentSystem.getInfo());
	}

	/**
	 * インデックスキューにたまっている数を返します。
	 * 
	 * @param req
	 *            ... 引数なし
	 */
	public void getIndexQueue(Request req) {
		logger.finest("getIndexQueue - called.");
		req.sendResult(IntegerType.create(parentSystem.getQueueSize(0)));
	}

	/**
	 * サーチキューにたまっている数を返します。
	 * 
	 * @param req
	 *            ... 引数なし
	 */
	public void getSearchQueue(Request req) {
		logger.finest("getSearchQueue - called.");
		req.sendResult(IntegerType.create(parentSystem.getQueueSize(1)));
	}

}
